/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.androsz.electricsleepbeta.alarmclock;

import java.text.SimpleDateFormat;
import java.util.Date;

import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Parcel;
import android.os.PowerManager;

import com.androsz.electricsleepbeta.R;
import com.androsz.electricsleepbeta.util.SharedWakeLock;

/**
 * Glue class: connects AlarmAlert IntentReceiver to AlarmAlert activity. Passes
 * through Alarm ID.
 */
public class AlarmReceiver extends BroadcastReceiver {

	/**
	 * If the alarm is older than STALE_WINDOW seconds, ignore. It is probably
	 * the result of a time or timezone change
	 */
	private final static int STALE_WINDOW = 60 * 30;

	private NotificationManager getNotificationManager(final Context context) {
		return (NotificationManager) context
				.getSystemService(Context.NOTIFICATION_SERVICE);
	}

	@Override
	public void onReceive(final Context context, final Intent intent) {
		if (Alarms.ALARM_KILLED.equals(intent.getAction())) {
			// The alarm has been killed, update the notification
			updateNotification(context,
					(Alarm) intent
							.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA),
					intent.getIntExtra(Alarms.ALARM_KILLED_TIMEOUT, -1));
			return;
		} else if (Alarms.CANCEL_SNOOZE.equals(intent.getAction())) {
			context.sendBroadcast(new Intent(
					Alarms.ALARM_SNOOZE_CANCELED_BY_USER_ACTION));
			Alarms.saveSnoozeAlert(context, -1, -1);
			return;
		}

		Alarm alarm = null;
		// Grab the alarm from the intent. Since the remote AlarmManagerService
		// fills in the Intent to add some extra data, it must unparcel the
		// Alarm object. It throws a ClassNotFoundException when unparcelling.
		// To avoid this, do the marshalling ourselves.
		final byte[] data = intent.getByteArrayExtra(Alarms.ALARM_RAW_DATA);
		if (data != null) {
			final Parcel in = Parcel.obtain();
			in.unmarshall(data, 0, data.length);
			in.setDataPosition(0);
			alarm = Alarm.CREATOR.createFromParcel(in);
		}

		if (alarm == null) {
			Log.v("AlarmReceiver failed to parse the alarm from the intent");
			return;
		}

		// Intentionally verbose: always log the alarm time to provide useful
		// information in bug reports.
		final long now = System.currentTimeMillis();
		final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss.SSS aaa");
		Log.v("AlarmReceiver.onReceive() id " + alarm.id + " setFor "
				+ format.format(new Date(alarm.time)));

		if (now > alarm.time + STALE_WINDOW * 1000) {
			if (Log.LOGV) {
				Log.v("AlarmReceiver ignoring stale alarm");
			}
			return;
		}

		// Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can
		// pick it up.
		SharedWakeLock.acquire(context, PowerManager.PARTIAL_WAKE_LOCK
				| PowerManager.ACQUIRE_CAUSES_WAKEUP
				| PowerManager.ON_AFTER_RELEASE);

		/* Close dialogs and window shade */
		final Intent closeDialogs = new Intent(
				Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
		context.sendBroadcast(closeDialogs);

		// Decide which activity to start based on the state of the keyguard.
		Class c = AlarmAlert.class;
		final KeyguardManager km = (KeyguardManager) context
				.getSystemService(Context.KEYGUARD_SERVICE);
		if (km.inKeyguardRestrictedInputMode()) {
			// Use the full screen activity for security.
			c = AlarmAlertFullScreen.class;
		}

		/*
		 * launch UI, explicitly stating that this is not due to user action so
		 * that the current app's notification management is not disturbed
		 */
		final Intent alarmAlert = new Intent(context, c);
		alarmAlert.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
		alarmAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
				| Intent.FLAG_ACTIVITY_NO_USER_ACTION);
		context.startActivity(alarmAlert);

		// Disable the snooze alert if this alarm is the snooze.
		Alarms.disableSnoozeAlert(context, alarm.id);
		// Disable this alarm if it does not repeat.
		if (!alarm.daysOfWeek.isRepeatSet()) {
			Alarms.enableAlarm(context, alarm.id, false);
		} else {
			// Enable the next alert if there is one. The above call to
			// enableAlarm will call setNextAlert so avoid calling it twice.
			Alarms.setTimeToIgnore(context, alarm, alarm.time);
			Alarms.setNextAlert(context);
		}

		// Play the alarm alert and vibrate the device.
		final Intent playAlarm = new Intent(Alarms.ALARM_ALERT_ACTION);
		playAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
		context.startService(playAlarm);

		// Trigger a notification that, when clicked, will show the alarm alert
		// dialog. No need to check for fullscreen since this will always be
		// launched from a user action.
		final Intent notify = new Intent(context, AlarmAlert.class);
		notify.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
		final PendingIntent pendingNotify = PendingIntent.getActivity(context,
				alarm.id, notify, 0);

		// Use the alarm's label or the default label as the ticker text and
		// main text of the notification.
		final String label = alarm.getLabelOrDefault(context);
		final Notification n = new Notification(R.drawable.stat_notify_alarm,
				label, alarm.time);
		n.setLatestEventInfo(context, label,
				context.getString(R.string.alarm_notify_text), pendingNotify);
		n.flags |= Notification.FLAG_SHOW_LIGHTS
				| Notification.FLAG_ONGOING_EVENT;
		n.defaults |= Notification.DEFAULT_LIGHTS;

		// Send the notification using the alarm id to easily identify the
		// correct notification.
		final NotificationManager nm = getNotificationManager(context);
		nm.notify(alarm.id, n);
	}

	private void updateNotification(final Context context, final Alarm alarm,
			final int timeout) {
		final NotificationManager nm = getNotificationManager(context);

		// If the alarm is null, just cancel the notification.
		if (alarm == null) {
			if (Log.LOGV) {
				Log.v("Cannot update notification for killer callback");
			}
			return;
		}

		// Launch SetAlarm when clicked.
		final Intent viewAlarm = new Intent(context, SetAlarm.class);
		viewAlarm.putExtra(Alarms.ALARM_ID, alarm.id);
		final PendingIntent intent = PendingIntent.getActivity(context,
				alarm.id, viewAlarm, 0);

		// Update the notification to indicate that the alert has been
		// silenced.
		final String label = alarm.getLabelOrDefault(context);
		final Notification n = new Notification(R.drawable.stat_notify_alarm,
				label, alarm.time);
		n.setLatestEventInfo(
				context,
				label,
				context.getString(R.string.alarm_alert_alert_silenced, timeout),
				intent);
		n.flags |= Notification.FLAG_AUTO_CANCEL;
		// We have to cancel the original notification since it is in the
		// ongoing section and we want the "killed" notification to be a plain
		// notification.
		nm.cancel(alarm.id);
		nm.notify(alarm.id, n);
	}
}